//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 015/01/2013.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog. If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef note_recog_EventCollection_h
#define note_recog_EventCollection_h

#include <set>
#include <string>
#include <sstream>


template<typename EventType>
class EventCollection : public IEventListener<EventType>
{
private:
    typedef std::set<EventType> Values;
    Values _events;
public:
    struct Difference
    {
    public:
        enum DiffType
        {
            LEFT_ONLY = 0,
            RIGHT_ONLY
        };
        const DiffType type;
        const EventType left;
        const EventType right;
        Difference(DiffType type, const EventType& left, const EventType& right) : type(type), left(left), right(right) {}
        ~Difference() {}
        bool operator==(const Difference& rhs) const
        {
            return type==rhs.type && left==rhs.left && right==rhs.right;
        }
        bool operator<(const Difference& rhs) const
        {
            if(type!=rhs.type)
                return type < rhs.type;
            if(left<rhs.left or rhs.left<left)
                return left < rhs.left;
            return right < rhs.right;
        }
        std::string to_string() const
        {
            std::ostringstream oss("EventCollection::Diff{");
            switch( type )
            {
                case LEFT_ONLY:
                    oss << "LEFT_ONLY:" << left;
                    break;
                case RIGHT_ONLY:
                    oss << "RIGHT_ONLY:" << right;
                    break;
                default:
                    oss << "UNKNOWN";
            }
            oss << "}";
            return oss.str();
        }
    };
    
public:
    EventCollection(){}
    virtual ~EventCollection(){}
    EventCollection(const EventCollection&rhs ) : _events(rhs._events){}
    
private: //not implemented
    EventCollection& operator=(const EventCollection&);
    
public:    
    typedef std::set<Difference> Differences;
    typedef typename Values::const_iterator const_iterator;
    const_iterator begin() const {return _events.begin();}
    const_iterator end() const {return _events.end();}
    size_t size() const { return _events.size(); }
    void add(const EventType& evt){ _events.insert(evt); }
    virtual void onEvent(const EventType& evt) { add(evt); }
    void remove(const EventType& note) { _events.erase(note); }
    
    std::vector<float> timeStamps() const
    {
        std::vector<float> times;
        for(auto &it : _events)
        {
            float t=it.getTimeStamp();
            times.push_back(t);
        }
        return times;
    }
    
    const EventType* findMatch(const EventType& evt) const
    {
        for(typename Values::const_iterator it=begin(); it!=end(); ++it)
        {
            const EventType& candidate = *it;
            if(candidate.approxEqual(evt))
            {
                return &candidate;
            }
        }
        return 0;
    }
    
    Differences compare(const EventCollection<EventType>& rhs) const
    {
        Values lhs_unmatched;
        for(typename Values::const_iterator it = begin(); it != end(); ++it)
        {
            lhs_unmatched.insert(*it);
        }
        
        Values rhs_unmatched;
        for(typename Values::const_iterator it = rhs.begin(); it != rhs.end(); ++it)
        {
            rhs_unmatched.insert(*it);
        }
        
        // remove left matches from right
        for(typename Values::const_iterator it=begin(); it!=end(); ++it)
        {
            const EventType& candidate = *it;
            const EventType* match = rhs.findMatch(candidate);
            if(match)
            {
                rhs_unmatched.erase(*match);
            }
        }
        
        // remove right matches from left
        for(typename Values::const_iterator it=rhs.begin(); it!=rhs.end(); ++it)
        {
            const EventType& candidate = *it;
            const EventType* match = findMatch(candidate);
            if(match)
            {
                lhs_unmatched.erase(*match);
            }
        }
        
        Differences results;
        for(typename Values::const_iterator it = lhs_unmatched.begin(); it != lhs_unmatched.end(); ++it)
        {
            Difference diff(Difference::LEFT_ONLY, *it, 0);
            results.insert(diff);
        }
        for(typename Values::const_iterator it=rhs_unmatched.begin(); it!=rhs_unmatched.end(); ++it)
        {
            Difference diff(Difference::RIGHT_ONLY, 0, *it);
            results.insert(diff);
        }
        return results;
    }
};


template<typename T>
std::ostream& operator<<(std::ostream& os, EventCollection<T> const& events);

template<typename T>
std::ostream& operator<<(std::ostream& os, EventCollection<T> const& events)
{
    os << "EventCollection{";
    for(typename EventCollection<T>::const_iterator it = events.begin(); it!=events.end(); ++it)
    {
        if(it != events.begin())
        {
            os << ",";
        }
        os << *it;
    }
    return os << "}";
}


template<typename T>
std::ostream& operator<<(std::ostream&, const std::set<T>&);

template<typename T>
std::ostream& operator<<(std::ostream& os, const std::set<T>& diffs)
{
    os << "set{";
    for(typename std::set<T>::const_iterator it = diffs.begin(); it != diffs.end(); ++it)
    {
        if(it != diffs.begin())
        {
            os << ",";
        }
        os << it->to_string();
    }
    return os << "}";
}


#endif
